using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

using CubeBuilder.Terrain;

namespace CubeBuilder
{
    //Map generation uses Perlin Noise and sorts the cubes into chunks which are then rendered using a
    //static vertex buffer.
    //They chunks are also sorted into Bounding Boxes for culling
    //TODO: Texture Atlasing

    public class Game1 : Microsoft.Xna.Framework.Game
    {
        //Effects/Frustum
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        private StaticVBRenderer currentVBRenderer;
        private Effect staticVBEffect;
        BoundingFrustum bf;
        SpriteFont dfont;
        //Camera Angles / frame rate
        float px, py, pt, ap = 0;
        float cz = 75; 
        int frameRate = 0;
        float cx, cy;
        int frameCounter = 0;
        int centerX, centerY;
        TimeSpan elapsedTime = TimeSpan.Zero;
        float ay = 80;
        //Map seeds
        int seedx;
        //Map Dimentions
        const int cubenumbery = 5; //depth from sea (y)
        //number of chunks, must be a root number
        //Will set size of map
        static int width = 16;
        static int length = 16;
        static int chunks = 625;
        static double xychunks = Math.Sqrt((double)chunks);
        //Cube list / render array
        private StaticVBRenderer[,][] staticVBRenderer = new StaticVBRenderer[(int)xychunks, (int)xychunks][];
        private List<Vector3>[,] Grass;
        private List<Vector3>[,] Water;
        private List<Vector3>[,] Dirt;
        private List<Vector3>[,] Stone;
        // Content     
        BasicEffect cubeEffect;
        Texture2D[] Textures = new Texture2D[6];
        BoundingBox[,] thisqube = new BoundingBox[(int)xychunks, (int)xychunks];
        // Position related variables
        Vector3 cameraPosition = new Vector3(0, 3, 4);
        Vector3 modelPosition = Vector3.Zero;
        // Misc Declarations
        int drawn = 0;
        float rotation = 0.0f;
        Random r = new Random();
        float aspectRatio = 0.0f;
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content"; 
            //graphics.SynchronizeWithVerticalRetrace = false;
            IsFixedTimeStep = false;
            graphics.PreferredBackBufferHeight = 720;
            graphics.PreferredBackBufferWidth = 1280; 
            TargetElapsedTime = TimeSpan.FromTicks(333333);
        }

        protected override void Initialize()
        {
            //Textures
            Textures[2] = Content.Load<Texture2D>("Textures/dirt1");
            dfont = Content.Load<SpriteFont>("default");
            this.staticVBEffect = Content.Load<Effect>("StaticVertexBuffer");
            Textures[1] = Content.Load<Texture2D>("Textures/grasstop");
            Textures[3] = Content.Load<Texture2D>("Textures/water");
            Textures[0] = Content.Load<Texture2D>("Textures/Stone");
            //Call map generation, at bottom
            GenerateMap();

            base.Initialize();
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            aspectRatio = GraphicsDevice.Viewport.AspectRatio;
            cubeEffect = new BasicEffect(GraphicsDevice);

            //Enable textures
            cubeEffect.TextureEnabled = true;

            // Set the World matrix which defines the position of the cube
            cubeEffect.World = Matrix.CreateRotationY(MathHelper.ToRadians(rotation)) *
                Matrix.CreateRotationX(MathHelper.ToRadians(rotation)) * Matrix.CreateTranslation(modelPosition);
            cubeEffect.LightingEnabled = true;
            cubeEffect.DirectionalLight0.Enabled = true;

            //cubeEffect.FogEnabled = true;
            //cubeEffect.FogColor = Color.Gray.ToVector3();
            //cubeEffect.FogStart = 100;
            //cubeEffect.FogEnd = 150;
        }

        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        protected override void Update(GameTime gameTime)
        {
            //Camera rotations >.<
            cx = px + (float)Math.Sin(pt) * 5.0f;
            cy = py + (float)Math.Cos(pt) * 5.0f;

            //Mouse values/Center
            IsMouseVisible = false;
            KeyboardState keyState = Keyboard.GetState();
            MouseState mouseState = Mouse.GetState();
            centerX = Window.ClientBounds.Width / 2;
            centerY = Window.ClientBounds.Height / 2;
            Mouse.SetPosition(centerX, centerY);

            //Mouse functions
            if (mouseState.X < centerX)
            {
                pt += 0.07f;
            }

            if (mouseState.X > centerX)
            {
                pt -= 0.07f;
            }

            if (mouseState.Y < centerY)
            {
                cz += 0.1f;
            }

            if (mouseState.Y > centerY)
            {
                cz -= 0.1f;
            }

            //Keyboard movements

            if (keyState.IsKeyDown(Keys.Add))
            {
                ay -= 0.1f;
                ap -= 0.1f;
                cz -= 0.1f;
            }

            if (keyState.IsKeyDown(Keys.Subtract))
            {
                ay += 0.1f;
                ap += 0.1f;
                cz += 0.1f;
            }

            if (keyState.IsKeyDown(Keys.W))
            {
                px += (float)Math.Sin(pt) * 0.3f;
                py += (float)Math.Cos(pt) * 0.3f;
            }

            if (keyState.IsKeyDown(Keys.S))
            {
                px += (float)Math.Sin(pt) * -0.3f;
                py += (float)Math.Cos(pt) * -0.3f;
            }

            if (keyState.IsKeyDown(Keys.Escape))
            {
                this.Exit();
            }
            
            bf = new BoundingFrustum(cubeEffect.View * cubeEffect.Projection);
            elapsedTime += gameTime.ElapsedGameTime;

            if (elapsedTime > TimeSpan.FromSeconds(1))
            {
                elapsedTime -= TimeSpan.FromSeconds(1);
                frameRate = frameCounter;
                frameCounter = 0;
            }
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
            cubeEffect.View = Matrix.CreateLookAt(new Vector3(px, ay, py), new Vector3(cx, cz, cy), Vector3.Up);
            // Set the Projection matrix which defines how we see the scene (Field of view)
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            cubeEffect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, 1.0f, 1000.0f);
            frameCounter++;
            DrawCubes(); //Draw the chunks
        }

        public void DrawCubes()
        {
            foreach (EffectPass pass in cubeEffect.CurrentTechnique.Passes)
            {
                for (byte chunkx = 0; chunkx < xychunks; chunkx++)
                {
                    for (byte chunkz = 0; chunkz < xychunks; chunkz++)
                    {
                        if (InView(thisqube[chunkx, chunkz]) == false)
                        {
                            //Checks if chunk is visible first.. Very rough
                            continue;
                        }
                        //Checks to see which terrain type to render, until I do texture atlasing
                        for (int terraintype = 0; terraintype < 4; terraintype++)
                        {
                            if (terraintype == 0)
                                currentVBRenderer = this.staticVBRenderer[chunkx, chunkz][0];
                            if (terraintype == 1)
                                currentVBRenderer = this.staticVBRenderer[chunkx, chunkz][1];
                            if (terraintype == 2)
                                currentVBRenderer = this.staticVBRenderer[chunkx, chunkz][2];
                            if (terraintype == 3)
                                currentVBRenderer = this.staticVBRenderer[chunkx, chunkz][3];
                            if (currentVBRenderer == null)
                                continue;
                            //Render that buffer
                            currentVBRenderer.Render(cubeEffect.View, cubeEffect.Projection, Textures[terraintype]);
                            drawn++;
                        }
                    }
                }
                
            }
            //Draw fps and whatnot
            string guess = "Total Draws: " + drawn;
            string fps = string.Format("fps: {0}", frameRate);
            spriteBatch.Begin();
            spriteBatch.DrawString(dfont, guess, new Vector2(10, 10), Color.Red);
            spriteBatch.DrawString(dfont, fps, new Vector2(10, 20), Color.Red);
            spriteBatch.End();
            drawn = 0;
        }

        public void GenerateMap()
        {
            //Make seeds
            seedx = r.Next(60000);
            //Lists for cube storage
            Grass = new List<Vector3>[(int)xychunks, (int)xychunks];
            Dirt = new List<Vector3>[(int)xychunks, (int)xychunks];
            Water = new List<Vector3>[(int)xychunks, (int)xychunks];
            Stone = new List<Vector3>[(int)xychunks, (int)xychunks];
            //Initialize lists, buffer
            for (byte initx = 0; initx < xychunks; initx++)
            {
                for (byte inity = 0; inity < xychunks; inity++)
                {
                    Grass[initx,inity] = new List<Vector3>();
                    Dirt[initx, inity] = new List<Vector3>();
                    Water[initx, inity] = new List<Vector3>();
                    Stone[initx, inity] = new List<Vector3>();
                    staticVBRenderer[initx, inity] = new StaticVBRenderer[4];
                }
            }
            //Generate map in chunks
            for (byte chunkx = 0; chunkx < xychunks; chunkx++)
            {
                for (byte chunkz = 0; chunkz < xychunks; chunkz++)
                {
                    for (int x = 0 + (16 * (int)chunkx); x < width + (16 * (int)chunkx); x++)
                    {
                        for (int z = 0 + (16 * (int)chunkz); z < length + (16 * (int)chunkz); z++)
                        {
                            //perlin noise
                            float octave1 = PerlinSimplexNoise.noise((x + seedx) * 0.0001f, (z + seedx) * 0.0001f) * 0f;
                            float octave2 = PerlinSimplexNoise.noise((x + seedx) * 0.0005f, (z + seedx) * 0.0005f) * 0f;
                            float octave3 = PerlinSimplexNoise.noise((x + seedx) * 0.005f, (z + seedx) * 0.005f) * 0f;
                            float octave4 = PerlinSimplexNoise.noise((x + seedx) * 0.01f, (z + seedx) * 0.01f) * 20f;
                            float octave5 = PerlinSimplexNoise.noise((x + seedx) * 0.03f, (z + seedx) * 0.03f) * 5f;
                            //15,10 | 30, 8 <----- Octave 4/5 combos that are good so far
                            float lowerGroundHeight = octave1 + octave2 + octave3 + octave4 + octave5;
                            if ((55 + (int)lowerGroundHeight) <= 64) //Check if water
                            {
                                Water[chunkx,chunkz].Add(new Vector3(x, 56 + (int)lowerGroundHeight, z)); //Add to water list
                                if (54 + (int)lowerGroundHeight < 64)
                                {
                                    for (int tosea = (54 + (int)lowerGroundHeight); tosea < 65; tosea++) //Create lakes/oceans depth
                                    {
                                        Water[chunkx, chunkz].Add(new Vector3(x, tosea + 1, z)); //Also add to list
                                    }
                                }
                            }
                            else
                            {
                                Grass[chunkx, chunkz].Add(new Vector3(x, 55 + (int)lowerGroundHeight, z)); //Add grass to list
                            }
                            //This will fill in all cubes under ground level, disabled as of now because it overflows the buffers..

                            //for (int tobottom = (54 + (int)lowerGroundHeight); tobottom > (54 + (int)lowerGroundHeight) - 3; tobottom--)
                            //{
                            //    if (r.Next(100) > 50)
                            //    {
                            //        Dirt[chunkx, chunkz].Add(new Vector3(x, tobottom, z)); //Add to list
                            //    }
                            //    else
                            //    {
                            //        Stone[chunkx, chunkz].Add(new Vector3(x, tobottom, z)); //Add to list
                            //    }
                            //}
                        }
                    }
                }
            }
            //load the buffers
            for (byte chunkx = 0; chunkx < xychunks; chunkx++)
            {
                for (byte chunkz = 0; chunkz < xychunks; chunkz++)
                {
                    //Create buffers in chunks for rendering
                    if (Stone[chunkx, chunkz].Count != 0)
                        this.staticVBRenderer[chunkx, chunkz][0] = new StaticVBRenderer(GraphicsDevice, Textures[0], Stone[chunkx, chunkz], staticVBEffect);
                    if (Grass[chunkx, chunkz].Count != 0)
                        this.staticVBRenderer[chunkx, chunkz][1] = new StaticVBRenderer(GraphicsDevice, Textures[1], Grass[chunkx, chunkz], staticVBEffect);
                    if (Dirt[chunkx, chunkz].Count != 0)
                        this.staticVBRenderer[chunkx, chunkz][2] = new StaticVBRenderer(GraphicsDevice, Textures[2], Dirt[chunkx, chunkz], staticVBEffect);
                    if (Water[chunkx, chunkz].Count != 0)
                        this.staticVBRenderer[chunkx, chunkz][3] = new StaticVBRenderer(GraphicsDevice, Textures[3], Water[chunkx, chunkz], staticVBEffect);
                }
            }
            ////Make boudning boxes for the chunk culling. Need working on.
            int minx = 0;
            int minz = 0;
            for (int chunkx = 0; chunkx < xychunks; chunkx++)
            {
                for (int chunkz = 0; chunkz < xychunks; chunkz++)
                {
                    //box around the chunks of cubes
                    minx = chunkx * 16;
                    minz = chunkz * 16;
                    thisqube[chunkx, chunkz] = new BoundingBox(new Vector3(minx,0,minz),new Vector3(minx + 16,128,minz + 16));
                }
            }
        }

        bool InView(BoundingBox boundingSphere)
        {
            //check to see if the bouding box is visible
            return bf.Intersects(boundingSphere);
        }
    }
}
